﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;
using Notoric.Model.Elements;

namespace Notoric.Model.Packaging
{
    /// <summary>
    /// Base class for package parts whose data is formed by an XML document.
    /// This class is abstract.
    /// </summary>
    public abstract class XmlPackagePart : PackagePart
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="XmlPackagePart"/> instance.
        /// </summary>
        /// <param name="package">
        /// <para>
        /// Type: <see cref="Package"/>
        /// </para>
        /// <para>
        /// The parent package (the package that owns this part).
        /// </para>
        /// </param>
        /// <param name="implementationPart">
        /// <para>
        /// Type: <see cref="System.IO.Packaging.PackagePart"/>
        /// </para>
        /// <para>
        /// An existing part of the parent's <see cref="Notoric.Model.Packaging.Package.ImplementationPackage"/> that implements
        /// the core functionality of this part.
        /// </para>
        /// </param>
        /// <param name="createNew">
        /// <para>
        /// Type: <see cref="System.Boolean"/>
        /// </para>
        /// <para>
        /// Indicates whether the content of this part should be created from scratch or loaded from the part content.<br/>
        /// <see langword="true"/> means create a new part content, <see langword="false"/> means load the content from
        /// the physical storage.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="package"/> or <paramref name="implementationPart"/> is <see langword="null"/>.
        /// </exception>
        protected XmlPackagePart(Package package, System.IO.Packaging.PackagePart implementationPart, bool createNew)
            : this(package, null, implementationPart, createNew)
        {
            
        }

        /// <summary>
        /// Creates a new <see cref="XmlPackagePart"/> instance.
        /// </summary>
        /// <param name="package">
        /// <para>
        /// Type: <see cref="Package"/>
        /// </para>
        /// <para>
        /// The parent package (the package that owns this part).
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="PackagePart"/>
        /// </para>
        /// <para>
        /// A package part that is the logical owner of this part (may be <see langword="null"/>).
        /// </para>
        /// </param>
        /// <param name="implementationPart">
        /// <para>
        /// Type: <see cref="System.IO.Packaging.PackagePart"/>
        /// </para>
        /// <para>
        /// An existing part of the parent's <see cref="Notoric.Model.Packaging.Package.ImplementationPackage"/> that implements
        /// the core functionality of this part.
        /// </para>
        /// </param>
        /// <param name="createNew">
        /// <para>
        /// Type: <see cref="System.Boolean"/>
        /// </para>
        /// <para>
        /// Indicates whether the content of this part should be created from scratch or loaded from the part content.<br/>
        /// <see langword="true"/> means create a new part content, <see langword="false"/> means load the content from
        /// the physical storage.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="package"/> or <paramref name="implementationPart"/> is <see langword="null"/>.
        /// </exception>
        protected XmlPackagePart(Package package, PackagePart owner, System.IO.Packaging.PackagePart implementationPart, bool createNew)
            : base(package, owner, implementationPart)
        {
            document = new XmlDocument();
            this.createNew = createNew;

            if (createNew)
            {
                var xmlDecl = document.CreateXmlDeclaration("1.0", "utf-8", "no");
                document.AppendChild(xmlDecl);
                IsModified = true;
            }
            else
                document.Load(ImplementationPart.GetStream(FileMode.Open, FileAccess.Read));
        }

        #endregion

        #region PackagePart Members

        /// <inheritdoc/>
        protected override void FlushCore()
        {
            if (root == null && createNew)
            {
                root = CreateNewRoot();
                document.AppendChild(root.Element);
            }

            if (root != null)
            {
                var contentStream = GetStream(FileMode.Create);
                document.Save(contentStream);
                contentStream.Close();
            }
        }

        #endregion

        #region Protected Methods

        /// <summary>
        /// When overriden in a derived class, creates a new <see cref="ModelElement"/>
        /// that will be the <see cref="RootElement"/> of this <see cref="XmlPackagePart"/>
        /// and loads its data from a given <see cref="XmlElement"/>.
        /// </summary>
        /// <remarks>
        /// The <see cref="CreateAndLoadRoot"/> is a factory method.
        /// </remarks>
        /// <param name="element">
        /// <para>
        /// Type: <see cref="XmlElement"/>
        /// </para>
        /// <para>
        /// An XML element that contains a serialized representation of the root element of this part.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="ModelElement"/>
        /// </para>
        /// <para>
        /// The created root element.
        /// </para>
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="element"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// The <paramref name="element"/> does not contain valid data.
        /// </exception>
        protected abstract ModelElement CreateAndLoadRoot(XmlElement element);

        /// <summary>
        /// When overriden in a derived class, creates a new <see cref="ModelElement"/>
        /// that will be the <see cref="RootElement"/> of this <see cref="XmlPackagePart"/>
        /// and sets its data to default values.
        /// </summary>
        /// <remarks>
        /// The <see cref="CreateNewRoot"/> is a factory method.
        /// </remarks>
        /// <returns>
        /// <para>
        /// Type: <see cref="ModelElement"/>
        /// </para>
        /// <para>
        /// The created root element.
        /// </para>
        /// </returns>
        protected abstract ModelElement CreateNewRoot();

        #endregion

        #region Internal Properties

        /// <summary>
        /// Gets the <see cref="XmlDocument"/> that forms the data of the part.
        /// </summary>
        internal XmlDocument Document
        {
            get { return document; }
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the root element of the part.
        /// </summary>
        /// <remarks>
        /// The actual creation of the root element is left for the time
        /// when this property is read for the first time or the part is flushed.
        /// The dervied class's <see cref="CreateAndLoadRoot"/> and <see cref="CreateNewRoot"/>
        /// factory methods are responsible for creating the actual root element of correct type.
        /// </remarks>
        /// <value>
        /// <para>
        /// Type: <see cref="ModelElement"/>
        /// </para>
        /// <para>
        /// The root element of the part.
        /// </para>
        /// </value>
        public ModelElement RootElement
        {
            get 
            {
                if (root == null)
                {
                    if (createNew)
                    {
                        root = CreateNewRoot();
                        document.AppendChild(root.Element);
                    }
                    else
                        root = CreateAndLoadRoot(document.DocumentElement);
                }

                return root;
            }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Indicates whether the root element will be loaded from physical storage
        /// or created from scratch.
        /// </summary>
        private bool createNew;
        /// <summary>
        /// Holds the XML document that forms the data of this part.
        /// </summary>
        private XmlDocument document;
        /// <summary>
        /// Holds the root element of this part.
        /// </summary>
        private ModelElement root;

        #endregion
    }
}
